﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections;
using System.ComponentModel;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// Thread-safe lookup allows multiple threads to look up items by key, add items,
    /// and remove items in a thread-safe manner
    /// </summary>
    /// <typeparam name="TListType"></typeparam>
    /// <typeparam name="TIndex"></typeparam>
#if NO_SILVERLIGHT
    [TypeDescriptionProvider(typeof(HyperTypeDescriptionProvider))]
#endif
    [Serializable]
    [DataContract]
    public class ThreadSafeLookupNonRef<TIndex, TListType> : Lockable, INotifyCollectionChanged,
        IDictionary<TIndex, TListType>, IMultipleItems<TListType>, ISupportsCount
    {
        public override string ToString()
        {
            return Count.ToString();
        }

        // add track changes
        [NonSerialized, XmlIgnore]
        public bool Modified;

        [DataMember]
        public Dictionary<TIndex, TListType> Lookup = new Dictionary<TIndex, TListType>();

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Always)]
#endif
        public TListType[] ArrayOfItems { get { return AllItems.ToArray(); } }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Always)]
#endif
        public int Count { get { return Lookup.Count; } }

        public bool ContainsKey(TIndex key) { return Lookup.ContainsKey(key); }
        public bool TryGetValue(TIndex key, out TListType items) { return Lookup.TryGetValue(key, out items); }

        public bool Remove(KeyValuePair<TIndex, TListType> kvp)
        {
            return Remove(kvp.Key, kvp.Value);
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        public List<TListType> AllItems
        {
            get
            {
                List<TListType> items = new List<TListType>(Lookup.Count);

                AquireLock();
                {
                    foreach (KeyValuePair<TIndex, TListType> kvp in Lookup)
                        items.Add(kvp.Value);
                }
                ReleaseLock();

                return items;
            }
        }

        public bool AddMultiple(IEnumerable<TIndex> keys, IList<TListType> values)
        {
            List<TListType> addedItems = null;
            bool added = false;
            int n = 0;

            if (CollectionChanged != null)
                addedItems = new List<TListType>();

            AquireLock();
            {
                foreach (TIndex key in keys)
                {
                    if (!Lookup.ContainsKey(key))
                    {
                        Lookup.Add(key, values[n]);
                        if (addedItems != null) addedItems.Add(values[n]);
                        Modified = true;
                        added = true;
                    }
                    n++;
                }
            }
            ReleaseLock();

            if (added)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, addedItems);

            return added;
        }

        public bool Add(TIndex key, TListType item)
        {
            bool found = true;

            AquireLock();
            {
                if (!Lookup.ContainsKey(key))
                {
                    Lookup.Add(key, item);
                    Modified = true;
                    found = false;
                }
            }
            ReleaseLock();

            if (!found)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);

            return !found;
        }

        public void AddOrSet(TIndex key, TListType item)
        {
            if (Lookup.ContainsKey(key))
            {
                TListType old = default(TListType);
                Lookup.TryGetValue(key, out old);
                if (!old.Equals(item))
                {
                    Lookup[key] = item;
                    Modified = true;

                    if (CollectionChanged != null)
                        OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TListType>() { old, item });
                }
            }
            else
            {
                Add(key, item);
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
            }
        }

        public TListType this[TIndex key]
        {
            get
            {
                TListType item;

                // Not totally safe for lookups that frequently add/remove items.
                // For thread safety, surround the following line with AquireLock(); / ReleaseLock();
                if (!Lookup.TryGetValue(key, out item)) item = default(TListType);

                return item;
            }
            set
            {
                AddOrSet(key, value);
            }
        }

        public bool Remove(TIndex key, TListType item)
        {
            return Remove(key);
        }

        public bool Remove(TIndex key)
        {
            bool removed = false;

            if (!Lookup.ContainsKey(key))
                return false;

            TListType item = default(TListType);
            AquireLock();
            {
                if (Lookup.TryGetValue(key, out item))
                {
                    Lookup.Remove(key);
                    Modified = true;
                    removed = true;
                }
            }
            ReleaseLock();

            if (removed)
                OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);

            return removed;
        }

        public void Clear()
        {
            IList<TListType> items = null;

            if (CollectionChanged != null)
                items = new List<TListType>(AllItems);

            AquireLock();
            {
                Lookup.Clear();
                Modified = true;
            }
            ReleaseLock();

            OnNotifyCollectionChanged(NotifyCollectionChangedAction.Reset, items);
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

        public IEnumerator<KeyValuePair<TIndex, TListType>> GetEnumerator()
        {
            return Lookup.GetEnumerator();
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        public bool IsReadOnly { get { return false; } }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        public List<TIndex> Keys
        {
            get
            {
                List<TIndex> items = new List<TIndex>(Lookup.Count);
                AquireLock(); { Lookup.ForEach(kvp => items.Add(kvp.Key)); } ReleaseLock();
                return items;
            }
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TListType> changedItems)
        {
            if (CollectionChanged != null)
            {
                switch (action)
                {
                    case NotifyCollectionChangedAction.Add:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Remove:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems));
                        break;
                    case NotifyCollectionChangedAction.Replace:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action, changedItems[0], changedItems[1]));
                        break;
                    case NotifyCollectionChangedAction.Reset:
                        CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TListType>(action));
                        break;
                }
            }
        }

        public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TListType changedItem)
        {
            if (CollectionChanged != null)
                OnNotifyCollectionChanged(action, new List<TListType>() { changedItem });
        }

        #region INotifyCollectionChanged Members

#if NO_SILVERLIGHT
        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;
#else
        [field: NonSerialized]
        public event EventHandler<NotifyCollectionChangedEventArgsEx<TListType>> CollectionChanged;
#endif

        #endregion

        #region IDictionary<TIndex,TListType> Members

        void IDictionary<TIndex, TListType>.Add(TIndex key, TListType value)
        {
            this.Add(key, value);
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        ICollection<TIndex> IDictionary<TIndex, TListType>.Keys
        {
            get { return Lookup.Keys; }
        }

#if NO_SILVERLIGHT
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
#endif
        public ICollection<TListType> Values
        {
            get { return Lookup.Values; }
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        public void Add(KeyValuePair<TIndex, TListType> item)
        {
            Add(item.Key, item.Value);
        }

        public bool Contains(KeyValuePair<TIndex, TListType> item)
        {
            return Lookup.Contains(item);
        }

        #endregion

        #region ICollection<KeyValuePair<TIndex,TListType>> Members

        public void CopyTo(KeyValuePair<TIndex, TListType>[] array, int arrayIndex)
        {
            AquireLock();
            {
                foreach (KeyValuePair<TIndex, TListType> item in Lookup)
                {
                    array[arrayIndex++] = item;
                }
            }
            ReleaseLock();
        }

        #endregion
    }
}